package com.cauli.file.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cauli.file.component.FileComponent;
import com.cauli.file.component.FileDealComp;
import com.cauli.file.mapper.FileMapper;
import com.cauli.file.mapper.ImageMapper;
import com.cauli.file.mapper.PictureFileMapper;
import com.cauli.file.mapper.RecoveryFileMapper;
import com.cauli.file.mapper.UploadTaskDetailMapper;
import com.cauli.file.mapper.UploadTaskMapper;
import com.cauli.file.mapper.UserFileMapper;
import com.cauli.file.model.dto.file.DownloadFileDTO;
import com.cauli.file.model.dto.file.PreviewDTO;
import com.cauli.file.model.dto.file.UploadFileDTO;
import com.cauli.file.model.entity.FileEntity;
import com.cauli.file.model.entity.Image;
import com.cauli.file.model.entity.PictureFile;
import com.cauli.file.model.entity.RecoveryFile;
import com.cauli.file.model.entity.UploadTask;
import com.cauli.file.model.entity.UploadTaskDetail;
import com.cauli.file.model.entity.UserFile;
import com.cauli.file.model.vo.file.UploadFileVO;
import com.cauli.file.service.FileTransferService;
import com.cauli.file.utils.UserFileUtils;
import com.qiwenshare.common.util.MimeUtils;
import com.qiwenshare.ufop.constant.StorageTypeEnum;
import com.qiwenshare.ufop.constant.UploadFileStatusEnum;
import com.qiwenshare.ufop.exception.operation.DownloadException;
import com.qiwenshare.ufop.exception.operation.UploadException;
import com.qiwenshare.ufop.factory.UFOPFactory;
import com.qiwenshare.ufop.operation.delete.Deleter;
import com.qiwenshare.ufop.operation.delete.domain.DeleteFile;
import com.qiwenshare.ufop.operation.download.Downloader;
import com.qiwenshare.ufop.operation.download.domain.DownloadFile;
import com.qiwenshare.ufop.operation.preview.Previewer;
import com.qiwenshare.ufop.operation.preview.domain.PreviewFile;
import com.qiwenshare.ufop.operation.upload.Uploader;
import com.qiwenshare.ufop.operation.upload.domain.UploadFile;
import com.qiwenshare.ufop.operation.upload.domain.UploadFileResult;
import com.qiwenshare.ufop.util.UFOPUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author Cauli
 * @date 2023-03-20 12:00
 * @description 文件传输 服务实现类
 */
@Slf4j
@Service
public class FileTransferServiceImpl extends ServiceImpl<FileMapper, FileEntity> implements FileTransferService {
    @Autowired
    private UserFileMapper userFileMapper;

    @Autowired
    private UploadTaskDetailMapper uploadTaskDetailMapper;

    @Autowired
    private UploadTaskMapper uploadTaskMapper;

    @Autowired
    private ImageMapper imageMapper;

    @Autowired
    private PictureFileMapper pictureFileMapper;

    @Autowired
    private RecoveryFileMapper recoveryFileMapper;

    @Autowired
    private FileDealComp fileDealComp;

    @Resource
    private UFOPFactory ufopFactory;

    @Override
    public UploadFileVO uploadFileSpeed(UploadFileDTO uploadFileDTO) {
        UploadFileVO uploadFileVo = new UploadFileVO();
        Map<String, Object> param = new HashMap<>();
        param.put("identifier", uploadFileDTO.getIdentifier());
        List<FileEntity> list = baseMapper.selectByMap(param);

        String filePath = uploadFileDTO.getFilePath();
        String relativePath = uploadFileDTO.getRelativePath();
        FileComponent fileComponent;
        if (relativePath.contains("/")) {
            fileComponent = new FileComponent(filePath, relativePath, false);
        } else {
            fileComponent = new FileComponent(filePath, uploadFileDTO.getFilename(), false);
        }

        if (!CollectionUtil.isEmpty(list)) {
            FileEntity file = list.get(0);

            // 查找userFile中有没有相应的文件
            UserFile userFile = new UserFile(fileComponent, StpUtil.getLoginIdAsLong(), file.getFileId());
            UserFile param1 = UserFileUtils.searchUserFileParam(userFile);
            List<UserFile> userFileList = userFileMapper.selectList(new QueryWrapper<>(param1));

            if (userFileList.size() <= 0) {
                // 查询不到，表示文件不存在，则将文件插入
                userFileMapper.insert(userFile);
                // ------------------------更新ES数据------------------------
                fileDealComp.uploadES(userFile.getUserFileId());
            } else if (userFileList.get(0).getDeleteFlag() == 1) {
                // 查询到了，但是在回收站，则将文件还原
                UserFile updateUserFile = userFileList.get(0);
                updateUserFile.setDeleteFlag(0);
                userFileMapper.updateById(updateUserFile);

                // 将回收站的文件删除
                LambdaQueryWrapper<RecoveryFile> recoveryFileLambdaQueryWrapper = new LambdaQueryWrapper<>();
                recoveryFileLambdaQueryWrapper.eq(RecoveryFile::getUserFileId, updateUserFile.getUserFileId());
                RecoveryFile recoveryFile = recoveryFileMapper.selectOne(recoveryFileLambdaQueryWrapper);
                recoveryFileMapper.deleteById(recoveryFile);
            }
            if (relativePath.contains("/")) {
                fileDealComp.restoreParentFilePath(fileComponent, StpUtil.getLoginIdAsLong());
            }
            uploadFileVo.setSkipUpload(true);
        } else {
            uploadFileVo.setSkipUpload(false);
            List<Integer> uploaded = uploadTaskDetailMapper.selectUpLoadedChunkNumList(uploadFileDTO.getIdentifier());
            if (uploaded != null && !uploaded.isEmpty()) {
                uploadFileVo.setUploaded(uploaded);
            } else {
                LambdaQueryWrapper<UploadTask> lambdaQueryWrapper = new LambdaQueryWrapper<>();
                lambdaQueryWrapper.eq(UploadTask::getIdentifier, uploadFileDTO.getIdentifier());
                List<UploadTask> rslist = uploadTaskMapper.selectList(lambdaQueryWrapper);
                if (rslist == null || rslist.isEmpty()) {
                    UploadTask uploadTask = new UploadTask();
                    uploadTask.setIdentifier(uploadFileDTO.getIdentifier());
                    uploadTask.setUploadStatus(UploadFileStatusEnum.UNCOMPLATE.getCode());
                    uploadTask.setFileName(fileComponent.getNameNotExtend());
                    uploadTask.setFilePath(fileComponent.getParent());
                    uploadTask.setExtendName(fileComponent.getExtendName());
                    uploadTask.setUserId(StpUtil.getLoginIdAsLong());
                    uploadTaskMapper.insert(uploadTask);
                }
            }
        }
        return uploadFileVo;
    }

    @Override
    public void uploadFile(HttpServletRequest request, UploadFileDTO uploadFileDto, Long userId) {
        UploadFile uploadFile = new UploadFile();
        uploadFile.setChunkNumber(uploadFileDto.getChunkNumber());
        uploadFile.setChunkSize(uploadFileDto.getChunkSize());
        uploadFile.setTotalChunks(uploadFileDto.getTotalChunks());
        uploadFile.setIdentifier(uploadFileDto.getIdentifier());
        uploadFile.setTotalSize(uploadFileDto.getTotalSize());
        uploadFile.setCurrentChunkSize(uploadFileDto.getCurrentChunkSize());

        Uploader uploader = ufopFactory.getUploader();
        if (uploader == null) {
            log.error("上传失败，请检查storageType是否配置正确");
            throw new UploadException("上传失败");
        }
        List<UploadFileResult> uploadFileResultList;
        try {
            uploadFileResultList = uploader.upload(request, uploadFile);
        } catch (Exception e) {
            log.error("上传失败，请检查UFOP连接配置是否正确");
            throw new UploadException("上传失败", e);
        }
        for (int i = 0; i < uploadFileResultList.size(); i++) {
            UploadFileResult uploadFileResult = uploadFileResultList.get(i);
            String relativePath = uploadFileDto.getRelativePath();
            FileComponent fileComponent;
            if (relativePath.contains("/")) {
                fileComponent = new FileComponent(uploadFileDto.getFilePath(), relativePath, false);
            } else {
                fileComponent = new FileComponent(uploadFileDto.getFilePath(), uploadFileDto.getFilename(), false);
            }

            if (UploadFileStatusEnum.SUCCESS.equals(uploadFileResult.getStatus())) {
                FileEntity fileEntity = new FileEntity(uploadFileResult);
                baseMapper.insert(fileEntity);

                UserFile userFile = new UserFile(fileComponent, userId, fileEntity.getFileId());

                UserFile param = UserFileUtils.searchUserFileParam(userFile);
                List<UserFile> userFileList = userFileMapper.selectList(new QueryWrapper<>(param));
                if (userFileList.size() > 0) {
                    String fileName = fileDealComp.getRepeatFileName(userFile, userFile.getFilePath());
                    userFile.setFileName(fileName);
                }
                userFileMapper.insert(userFile);

                if (relativePath.contains("/")) {
                    fileDealComp.restoreParentFilePath(fileComponent, userId);
                }
                // ------------------------更新ES数据------------------------
                fileDealComp.uploadES(userFile.getUserFileId());

                LambdaQueryWrapper<UploadTaskDetail> lambdaQueryWrapper = new LambdaQueryWrapper<>();
                lambdaQueryWrapper.eq(UploadTaskDetail::getIdentifier, uploadFileDto.getIdentifier());
                uploadTaskDetailMapper.delete(lambdaQueryWrapper);

                LambdaUpdateWrapper<UploadTask> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
                lambdaUpdateWrapper.set(UploadTask::getUploadStatus, UploadFileStatusEnum.SUCCESS.getCode())
                        .eq(UploadTask::getIdentifier, uploadFileDto.getIdentifier());
                uploadTaskMapper.update(null, lambdaUpdateWrapper);

                try {
                    if (UFOPUtils.isImageFile(uploadFileResult.getExtendName())) {
                        BufferedImage src = uploadFileResult.getBufferedImage();
                        Image image = new Image();
                        image.setImageWidth(src.getWidth());
                        image.setImageHeight(src.getHeight());
                        image.setFileId(fileEntity.getFileId());
                        imageMapper.insert(image);
                    }
                } catch (Exception e) {
                    log.error("生成图片缩略图失败！", e);
                }
                fileDealComp.parseMusicFile(uploadFileResult.getExtendName(), uploadFileResult.getStorageType().getCode(), uploadFileResult.getFileUrl(), fileEntity.getFileId());
            } else if (UploadFileStatusEnum.UNCOMPLATE.equals(uploadFileResult.getStatus())) {
                UploadTaskDetail uploadTaskDetail = new UploadTaskDetail();
                uploadTaskDetail.setFilePath(fileComponent.getParent());
                uploadTaskDetail.setFilename(fileComponent.getNameNotExtend());
                uploadTaskDetail.setChunkNumber(uploadFileDto.getChunkNumber());
                uploadTaskDetail.setChunkSize((int) uploadFileDto.getChunkSize());
                uploadTaskDetail.setRelativePath(uploadFileDto.getRelativePath());
                uploadTaskDetail.setTotalChunks(uploadFileDto.getTotalChunks());
                uploadTaskDetail.setTotalSize((int) uploadFileDto.getTotalSize());
                uploadTaskDetail.setIdentifier(uploadFileDto.getIdentifier());
                uploadTaskDetailMapper.insert(uploadTaskDetail);
            } else if (UploadFileStatusEnum.FAIL.equals(uploadFileResult.getStatus())) {
                LambdaQueryWrapper<UploadTaskDetail> lambdaQueryWrapper = new LambdaQueryWrapper<>();
                lambdaQueryWrapper.eq(UploadTaskDetail::getIdentifier, uploadFileDto.getIdentifier());
                uploadTaskDetailMapper.delete(lambdaQueryWrapper);

                LambdaUpdateWrapper<UploadTask> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
                lambdaUpdateWrapper.set(UploadTask::getUploadStatus, UploadFileStatusEnum.FAIL.getCode())
                        .eq(UploadTask::getIdentifier, uploadFileDto.getIdentifier());
                uploadTaskMapper.update(null, lambdaUpdateWrapper);
            }
        }
    }

    private String formatChatset(String str) {
        if (str == null) {
            return "";
        }
        if (Charset.forName("ISO-8859-1").newEncoder().canEncode(str)) {
            byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1);
            return new String(bytes, Charset.forName("GBK"));
        }
        return str;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void downloadFile(HttpServletResponse httpServletResponse, DownloadFileDTO downloadFileDTO) {
        UserFile userFile = userFileMapper.selectById(downloadFileDTO.getUserFileId());

        if (userFile.isFile()) {
            FileEntity fileEntity = baseMapper.selectById(userFile.getFileId());
            Downloader downloader = ufopFactory.getDownloader(fileEntity.getStorageType());
            if (downloader == null) {
                log.error("下载失败，文件存储类型不支持下载，storageType:{}", fileEntity.getStorageType());
                throw new DownloadException("下载失败");
            }
            DownloadFile downloadFile = new DownloadFile();

            downloadFile.setFileUrl(fileEntity.getFileUrl());
            httpServletResponse.setContentLengthLong(fileEntity.getFileSize());
            downloader.download(httpServletResponse, downloadFile);
        } else {

            FileComponent fileComponent = new FileComponent(userFile.getFilePath(), userFile.getFileName(), true);
            List<UserFile> userFileList = userFileMapper.selectUserFileByLikeRightFilePath(fileComponent.getPath(), userFile.getUserId());
            List<Long> userFileIds = userFileList.stream().map(UserFile::getUserFileId).collect(Collectors.toList());

            downloadUserFileList(httpServletResponse, userFile.getFilePath(), userFile.getFileName(), userFileIds);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void downloadUserFileList(HttpServletResponse httpServletResponse, String filePath, String fileName, List<Long> userFileIds) {
        String staticPath = UFOPUtils.getStaticPath();
        String tempPath = staticPath + "temp" + File.separator;
        File tempDirFile = new File(tempPath);
        if (!tempDirFile.exists()) {
            tempDirFile.mkdirs();
        }

        FileOutputStream f = null;
        try {
            f = new FileOutputStream(tempPath + fileName + ".zip");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
        ZipOutputStream zos = new ZipOutputStream(csum);
        BufferedOutputStream out = new BufferedOutputStream(zos);

        try {
            for (Long userFileId : userFileIds) {
                UserFile userFile1 = userFileMapper.selectById(userFileId);
                if (userFile1.isFile()) {
                    FileEntity fileEntity = baseMapper.selectById(userFile1.getFileId());
                    Downloader downloader = ufopFactory.getDownloader(fileEntity.getStorageType());
                    if (downloader == null) {
                        log.error("下载失败，文件存储类型不支持下载，storageType:{}", fileEntity.getStorageType());
                        throw new UploadException("下载失败");
                    }
                    DownloadFile downloadFile = new DownloadFile();
                    downloadFile.setFileUrl(fileEntity.getFileUrl());
                    InputStream inputStream = downloader.getInputStream(downloadFile);
                    BufferedInputStream bis = new BufferedInputStream(inputStream);
                    try {
                        FileComponent fileComponent = new FileComponent(StrUtil.removePrefix(userFile1.getFilePath(), filePath), userFile1.getFileName() + "." + userFile1.getExtendName(), false);
                        zos.putNextEntry(new ZipEntry(fileComponent.getPath()));

                        byte[] buffer = new byte[1024];
                        int i = bis.read(buffer);
                        while (i != -1) {
                            out.write(buffer, 0, i);
                            i = bis.read(buffer);
                        }
                    } catch (IOException e) {
                        log.error("" + e);
                        e.printStackTrace();
                    } finally {
                        IOUtils.closeQuietly(bis);
                        try {
                            out.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                    FileComponent fileComponent = new FileComponent(StrUtil.removePrefix(userFile1.getFilePath(), filePath), userFile1.getFileName(), true);
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(fileComponent.getPath() + FileComponent.separator));
                    // 没有文件，不需要文件的copy
                    zos.closeEntry();
                }
            }

        } catch (Exception e) {
            log.error("压缩过程中出现异常:" + e);
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        String zipPath = "";
        try {
            Downloader downloader = ufopFactory.getDownloader(StorageTypeEnum.LOCAL.getCode());
            DownloadFile downloadFile = new DownloadFile();
            downloadFile.setFileUrl("temp" + File.separator + fileName + ".zip");
            File tempFile = new File(UFOPUtils.getStaticPath() + downloadFile.getFileUrl());
            httpServletResponse.setContentLengthLong(tempFile.length());
            downloader.download(httpServletResponse, downloadFile);
            zipPath = UFOPUtils.getStaticPath() + "temp" + File.separator + fileName + ".zip";
        } catch (Exception e) {
            // org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
            if (e.getMessage().contains("ClientAbortException")) {
                //该异常忽略不做处理
            } else {
                log.error("下传zip文件出现异常：{}", e.getMessage());
            }
        } finally {
            File file = new File(zipPath);
            if (file.exists()) {
                file.delete();
            }
        }
    }

    @Override
    public void previewFile(HttpServletResponse httpServletResponse, PreviewDTO previewDTO) {
        UserFile userFile = userFileMapper.selectById(previewDTO.getUserFileId());
        FileEntity fileEntity = baseMapper.selectById(userFile.getFileId());
        Previewer previewer = ufopFactory.getPreviewer(fileEntity.getStorageType());
        if (previewer == null) {
            log.error("预览失败，文件存储类型不支持预览，storageType:{}", fileEntity.getStorageType());
            throw new UploadException("预览失败");
        }
        PreviewFile previewFile = new PreviewFile();
        previewFile.setFileUrl(fileEntity.getFileUrl());
        try {
            if ("true".equals(previewDTO.getIsMin())) {
                previewer.imageThumbnailPreview(httpServletResponse, previewFile);
            } else {
                previewer.imageOriginalPreview(httpServletResponse, previewFile);
            }
        } catch (Exception e) {
            // org.apache.catalina.connector.ClientAbortException: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
            if (e.getMessage().contains("ClientAbortException")) {
                //该异常忽略不做处理
            } else {
                log.error("预览文件出现异常：{}", e.getMessage());
            }
        }
    }

    @Override
    public void previewPictureFile(HttpServletResponse httpServletResponse, PreviewDTO previewDTO) {
        byte[] bytesUrl = Base64.getDecoder().decode(previewDTO.getUrl());
        PictureFile pictureFile = new PictureFile();
        pictureFile.setFileUrl(new String(bytesUrl));
        pictureFile = pictureFileMapper.selectOne(new QueryWrapper<>(pictureFile));
        Previewer previewer = ufopFactory.getPreviewer(pictureFile.getStorageType());
        if (previewer == null) {
            log.error("预览失败，文件存储类型不支持预览，storageType:{}", pictureFile.getStorageType());
            throw new UploadException("预览失败");
        }
        PreviewFile previewFile = new PreviewFile();
        previewFile.setFileUrl(pictureFile.getFileUrl());
        try {
            String mime = MimeUtils.getMime(pictureFile.getExtendName());
            httpServletResponse.setHeader("Content-Type", mime);

            String fileName = pictureFile.getFileName() + "." + pictureFile.getExtendName();
            try {
                fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            // 设置文件名
            httpServletResponse.addHeader("Content-Disposition", "fileName=" + fileName);

            previewer.imageOriginalPreview(httpServletResponse, previewFile);
        } catch (Exception e) {
            // org.apache.catalina.connector.ClientAbortException: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
            if (e.getMessage().contains("ClientAbortException")) {
                // 该异常忽略不做处理
            } else {
                log.error("预览文件出现异常：{}", e.getMessage());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFile(FileEntity fileEntity) {
        Deleter deleter = ufopFactory.getDeleter(fileEntity.getStorageType());
        DeleteFile deleteFile = new DeleteFile();
        deleteFile.setFileUrl(fileEntity.getFileUrl());
        deleter.delete(deleteFile);
    }

    @Override
    public Long selectStorageSizeByUserId(Long userId) {
        return userFileMapper.selectStorageSizeByUserId(userId);
    }
}
